package com.huiyeji.dynamic.core;

import com.huiyeji.dynamic.core.exception.DynamicException;
import com.huiyeji.dynamic.core.exception.DynamicStatementExecuteException;
import com.huiyeji.dynamic.core.exception.DynamicStatementParseException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Huiyeji
 */
public class PluginEngine extends DynamicEngine{

    private final String name;

    private final DynamicContext parentContext;

    private final Map<String,Method> methods = new HashMap<>();

    private final Map<String,DynamicEngine> methodEngines = new HashMap<>();

    private final List<Thread> threads = new ArrayList<>();

    public PluginEngine(String name,List<String> initializeStatement, Map<String,List<String>> methodStatements, DynamicContext parentContext,PluginObject pluginObject) {
        super(parentContext.getPluginManager());
        this.name = name;
        this.parentContext = parentContext;
        try {
            getContext().setPointer(parse(initializeStatement));
        } catch (Exception exception){
            throw new DynamicStatementParseException("Plugin 【" + name + "】 Initialize Parse Error : " + exception.getMessage());
        }

        for (Map.Entry<String, List<String>> entry : methodStatements.entrySet()) {
            try {
                DynamicStatementPointer pointer = parse(entry.getValue());
                Method method = new Method(entry.getKey(), pointer);
                methods.put(method.getName(), method);
            } catch (Exception exception){
                throw new DynamicStatementParseException("Plugin 【" + name + "】 Method 【"+entry.getKey()+"】 Parse : " + exception.getMessage());
            }
        }

        //初始化plugin，注册this
        pluginObject.setEngine(this);
        _registerThis(pluginObject);

        //注册root context到容器中
        DynamicObject dynamicObject = DynamicBuilderManager.Anno.build(parentContext);
        dynamicObject.setDynamicObjectName("root");
        getFactory().registerDynamicObject(dynamicObject);

        //全局bean穿透
        syncGlobalBean();

        //插件初始化
        initialize();
    }

    private void syncGlobalBean() {
        DynamicObjectFactory factory = getContext().getFactory();
        for (Map.Entry<String, DynamicObject> entry : parentContext.getFactory().getGlobalDynamicObjectMap().entrySet()) {
            if (!factory.containsDynamicObject(entry.getKey())){
                factory._register(entry.getValue(),true);
            }
        }
    }

    protected void _registerThis(PluginObject pluginObject){
        getFactory()._register("this",pluginObject,true);
    }

    public void initialize(){
        DynamicResult result = process();
        if (!result.isSuccess()){
            DynamicErrorResult errorResult = (DynamicErrorResult) result;
            throw new DynamicStatementExecuteException("Plugin 【" + name + "】 Initialize Error : "+errorResult.getExceptionMessage());
        }
        getContext()._globalization();
        getFactory()._globalization();

        //调用main方法
        if (methods.containsKey("main")){
            DynamicResult mainResult = processMethod("main");
            if (mainResult != null && !mainResult.isSuccess()){
                DynamicErrorResult errorResult = (DynamicErrorResult) mainResult;
                throw new RuntimeException("Plugin 【"+name+"】 Main Process Error : " + errorResult.getExceptionMessage());
            }
        }
    }

    public DynamicResult processMethod(String methodName,Object... args){
        if (!methods.containsKey(methodName)){
            throw new DynamicException(6399,"Plugin 【" + name + "】 Method 【"+methodName+"】 Not Found !");
        }
        Method method = methods.get(methodName);
        if (!methodEngines.containsKey(methodName)){
            methodEngines.put(methodName,new MethodEngine(this,method));
        }

        final DynamicEngine methodEngine = methodEngines.get(methodName);

        //加载插件方法参数
        if (args != null && args.length > 0){
            Map<String,Object> argMap = new HashMap<>();
            for (int i = 0; i < args.length; i++) {
                argMap.put("arg" + (i+1),args[i]);
            }
            methodEngine.getContext().tempLoad(argMap);
        }

        //支持异步调用
        if (method.isAsync()){
            Thread thread = new Thread(() -> {
                try {
                    DynamicResult result = methodEngine.process();
                    if (!result.isSuccess()) {
                        DynamicErrorResult errorResult = (DynamicErrorResult) result;
                        throw new DynamicException(6776, "Plugin 【" + name + "】 Async Method 【" + methodName + "】 Process Error : \n" + "【" + errorResult.getExceptionStatement() + "】: \n    " + errorResult.getExceptionMessage());
                    }
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            });
            thread.start();
            threads.add(thread);
            return null;
        }

        return methodEngine.process();
    }

    @Override
    public DynamicResult process(){
        DynamicStatementPointer pointer = getContext().getPointer();
        try {
            while (pointer.hasNext()){
                getContext().getExecutor().execute(getContext(),pointer.next());
            }
            return new DynamicSuccessResult(getContext());
        } catch (DynamicException dynamicException){
            return new DynamicErrorResult(dynamicException,getContext());
        } catch (Exception exception){
            return new DynamicErrorResult(exception,getContext());
        } finally {
            pointer.reset();
        }
    }

    protected void interrupt(){
        for (Thread thread : threads) {
            thread.interrupt();
        }
    }

    protected boolean haveMethod(String methodName){
        return methods.containsKey(methodName);
    }

    private static class Method {
        private final String name;

        private final boolean async;

        private final DynamicStatementPointer pointer;

        private Method(String originName, DynamicStatementPointer pointer) {
            String[] nameArr = originName.split("@");
            if (nameArr.length == 1) {
                name = nameArr[0];
                async = false;
            } else {
                name = nameArr[0];
                async = "async".equals(nameArr[1]);
            }
            this.pointer = pointer;
        }

        private String getName(){
            return name;
        }

        private boolean isAsync(){
            return async;
        }

        private DynamicStatementPointer getPointer(){
            return pointer;
        }
    }

    private static class MethodEngine extends DynamicEngine {

        private MethodEngine(PluginEngine pluginEngine,Method method){
            super(
                    pluginEngine.getContext().getGlobalContextData(),
                    DynamicObjectFactory.createFactory(),
                    pluginEngine.getContext().getPluginManager()
            );

            DynamicObjectFactory methodFactory = getFactory();

            //方法引擎全局bean穿透
            for (Map.Entry<String, DynamicObject> entry : pluginEngine.getContext().getFactory().getGlobalDynamicObjectMap().entrySet()) {
                if (!methodFactory.containsDynamicObject(entry.getKey())){
                    methodFactory._register(entry.getKey(),entry.getValue(),true);
                }
            }

            //初始化pointer
            getContext().setPointer(method.getPointer());
        }

        @Override
        public DynamicResult process() {
            DynamicStatementPointer pointer = getContext().getPointer();
            try {
                while (pointer.hasNext()){
                    getContext().getExecutor().execute(getContext(),pointer.next());
                }
                return new DynamicSuccessResult(getContext());
            } catch (DynamicException dynamicException){
                return new DynamicErrorResult(dynamicException,getContext());
            } catch (Exception exception){
                return new DynamicErrorResult(exception,getContext());
            } finally {
                getContext().tempClear(false);
            }
        }
    }
}
